Java Security(I) Reflection
Java安全可以从反序列化漏洞开始说起,反序列化漏洞⼜可以从反射开始说起。 ——phith0n
简介
对于java安全而言,反射是一个很重要的基础知识。我们可以通过对对象的反射来获得某一个Class,也可以通过对Class的反射获得他的所有方法(包括私有)。
在一些只有难以利用的基本类型对象(String, Char, Integer, ...)的情况下,利用反射获取到某些可以加以利用的Class 往往是一个不错的利用手段。
Exploitaion
反射中常用的方法
-
forName
获取类- 参数需要是目标Class的全名
-
newInstance
实例化一个对应Class的对象- 在目标类拥有无参构造函数的时候不需要参数即可使用
-
getMethod
获取对应Class的某个方法 -
invoke
执行方法- 一般来说至少会有一个参数,是使用这个方法的对象,后续参数是对于方法需要的参数
利用反射获取Class
- 如果上文存在对应Class的对象
obj
,那么直接使用obj.getClass()
方法即可。 - 通常来说不会存在对应的对象,所以需要用到
Class.forName("target-class-name")
来获取到目标类
(其实如果已经加载了某个类,比如说Test
,是可以直接使用Test.class
来拿到这个类的,不过此方法不属于反射就是了。而且通过此方法来获取对某个类的引用的时候,是不会触发类加载器(ClassLoader
)对他的初始化操作的,而通过forName
来引用时则会默认触发,这事实上和forName
方法的一个重载有关)
只是说的话比较枯燥,下面来看几个demo理解一下吧:
demo1-使用obj.getClass()
reflection
类的内容如下(后续demo也会基于这个类来示范):
public class reflection {
public void hello(){
System.out.println("hello!");
}
private void secret(){
System.out.println("You find me!!");
}
}
现在我们要用反射获取到他的类,并使用他的hello方法。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
reflection a = new reflection();
a.hello();
Class ref = a.getClass();
ref.getMethod("hello").invoke(a);
}
}
由于hello方法是公有的,所以这里使用了两种方法来调用它。
demo2-使用forName()
在上下文没有reflection
类的时候也能够调用他的方法。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class ref = Class.forName("reflection");
ref.getMethod("hello").invoke(ref.newInstance());
}
}
demo3-对私有方法的反射
在reflection
类中还有一个私有方法secret()
,我们无法使用getMethod
来获取到他。
此时就可以使用getDeclaredMethod
方法强行获取,然后再用setAccessible(true)
赋予执行权限。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class ref = Class.forName("reflection");
Method secr = ref.getDeclaredMethod("secret");
secr.setAccessible(true);
secr.invoke(ref.newInstance());
}
}
(我这里使用的是jdk 8u341
,此方法在高版本可能并不适用)
浅谈利用反射的RCE思路
使用forName反射java.lang.Runtime类
在java.lang.Runtime
类中有exec
方法,可以直接使用一些系统命令。
不过要注意的是java.lang.Runtime
类在设计上使用的是单例模式,我们想使用invoke
的时候还需要传一个该类型的对象进去,此时newInstance
方法是无法使用的。所以还需要先反射得到getRuntime
这个方法来获取一个对象。
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class runtime = Class.forName("java.lang.Runtime");
Method getruntime = runtime.getMethod("getRuntime");
Object obj = getruntime.invoke(runtime);
runtime.getMethod("exec", String.class).invoke(obj, "calc.exe");
}
}
运行成功将弹出计算器。
无默认无参构造函数下的利用
java.lang.Runtime
类的情况也是无法使用newInstance
的,此时我们可以用 getConstructor
来反射。
和getMethod
类似, getConstructor
接收的参数是构造函数列表类型,因为构造函数也支持重载,
所以必须用参数列表类型才能唯一确定一个构造函数。
此外,java.lang.ProcessBuilder
也是一种常用于RCE的函数,这里用它做一个示范
import java.lang.reflect.Method;
public class test {
public static void main(String[] args) throws Exception {
Class pb = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)pb.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();
}
}